import fs from "fs-extra";
import ip from "ip";
import path from "path";
import xlsx from 'xlsx';
import { ProjectType, TReportLv } from "./BuildParams.js";
import { CmdOption, TBuiltInTaskTpl } from "./CmdOption.js";
import { BaseTask } from "./common/tasks/BastTask.js";
import { toolchain } from "./toolchain.js";
import { env } from "./env.js";
import { Nullable } from "./typings.js";

export declare type TVersionInfo = {
    version: string,
    qrcodeFile?: string
}

export declare type IAdditionalMsg = {
    /**注意，在飞书端，markdown将转为interfactive，故格式必须符合飞书卡片规则 */
    type: 'text' | 'markdown'
    mention?: string
    msg: string
} | {
    type: 'file'
    file: string
}

declare type TTplSheet = {
    TEMPLATE: TBuiltInTaskTpl
} & {
    [key: string]: '√' | '×'
}

export abstract class Ancestor {
    protected series: BaseTask<any>[] = [];
    
    constructor(protected cmdOption: CmdOption) {
    }

    public abstract awake(): Promise<boolean>;
    public abstract start(): Promise<void>;

    protected async startSeries(): Promise<void> {
        let tpl: Nullable<TTplSheet> = null;
        if (toolchain.option.executeTpl) {
            tpl = await this.readBuiltInTpl();
            // 为防止新加了Task忘了维护模板表格，此处检查所有task均需显式配置
            for (const task of this.series) {
                if (!(task.taskName in tpl)) {
                    console.error(`built in template out of date! ${task.taskName} is not defined!`);
                    process.exit(1);
                }
            }
        }
        const skipStatus: { [name: string]: boolean } = {};
        for (const task of this.series) {
            if (task.taskName in skipStatus) {
                console.error(`[Series] Task ${task.taskName} repeated, Please check!`);
                process.exit(1);
            }

            if (!task.executeAlways && 
                (toolchain.params.skipTasks?.includes(task.taskName) || 
                toolchain.params.executeTasks != null && !toolchain.params.executeTasks.includes(task.taskName) || 
                tpl != null && tpl[task.taskName] == '×' ||
                task.skip)) {
                skipStatus[task.taskName] = true;
            } else {
                skipStatus[task.taskName] = false;

                if (task.dependencies != null) {
                    for (const dp of task.dependencies) {
                        console.log(`[Series] Task ${dp} cannot be skipped caurse it's depended by ${task.taskName}`);
                        skipStatus[dp] = false;
                    }
                }
            }
        }

        const willRunTasks: string[] = [];
        for (const taskName in skipStatus) {
            if (!skipStatus[taskName]) {
                willRunTasks.push(taskName);
            }
        }
        console.log(`[Series] The following task will be runned: ${willRunTasks.join(',')}`);

        for (const task of this.series) {
            if (skipStatus[task.taskName]) {
                console.log(`[${task.taskName}] task skipped ..............................`);
                continue;
            }
            console.log(`[${task.taskName}] task begin ..............................`);
            console.time(`[${task.taskName}]`);

            const result = await task.run();
            toolchain.taskHelper.saveResult(task.taskName, result);
            console.timeEnd(`[${task.taskName}]`);

            if (!result.success) {
                const message = result.message || 'task failed';
                console.error(`[${task.taskName}] ${message}`);
                process.exit(result.errorCode);
            }
        }
    }

    async getParams(): Promise<void> {
        const [projectName, projectType] = this._getProjInfo(this.cmdOption.projectName);
        if (this.cmdOption.skip) {
            toolchain.params.skipTasks = this.cmdOption.skip.split(/,\s*/);
        }
        if (this.cmdOption.execute) {
            toolchain.params.executeTasks = this.cmdOption.execute.split(/,\s*/);
        }
        console.log('The following task will be skipped:', toolchain.params.skipTasks ?? '');
        toolchain.params.ip = ip.address(env.ipName);
        toolchain.params.projectName = projectName;
        toolchain.params.projectType = projectType;

        // 发test默认JsonDiff
        const report = this.cmdOption.report || (projectType == 'publish' ? 'JsonDiff' : '');
        toolchain.params.reportLvs = report.split(/,\s*/) as TReportLv[];
        // if(this.cmdOption.platform == 'iOS') {
        //     toolchain.params.platformPath = 'platformIos';
        // } else {
        //     toolchain.params.platformPath = 'platform';
        // }
        toolchain.params.platformPath = 'platform';

        // 更新ini配置
        if (env.projIniRoot && fs.existsSync(env.projIniRoot)) {
            // 为防止多个任务同时操作导致svn报错，此处catch下
            await toolchain.svnClient.cleanup(false, env.projIniRoot).catch(e => {
                console.error(e);
                if (e.message.includes('E200033')) {
                    console.warn('cleanup proj ini failed, but it\'s ok!');
                }
            });
            await toolchain.svnClient.update(env.projIniRoot).catch(e => {
                console.error(e);
                if (e.message.includes('E200033')) {
                    console.warn('update proj ini failed, but it\'s ok!');
                }
            });
        }
        
        toolchain.params.iCfg = await toolchain.projIniCfgParser.getProjIniCfg(toolchain.params.projectName);
        await toolchain.hostInfoParser.getBuildCfg(this.cmdOption, toolchain.params);
        await toolchain.svnCfgParser.getSVNCfg(this.cmdOption, toolchain.params);
    }

    private _getProjInfo(projectName: string) {
        return projectName.split('-') as [projectName: string, projectType: ProjectType];
    }

    protected async cleanProj(workspacePath: string, responsitory: string) {
        console.time('SVN更新耗时');
        console.log('=============================clean project===============================');
        // 有些项目svn可能没有project这一级，比如一些临时拉的测试项目
        // 先检测是否有project
        let svnContainsProject = true;
        try {
            await toolchain.svnClient.info(responsitory + '/project', '--show-item', 'kind');
        } catch(e) {
            if (e instanceof Error) {
                if (e.message.includes('svn: E200009: Could not display info for all targets because some targets don\'t exist')) {
                    svnContainsProject = false;
                }
            }
        }
        console.log('client svn contains project node? ' + (svnContainsProject ? 'Y' : 'N'));
        let rootpath = workspacePath;
        if (svnContainsProject && rootpath.endsWith('project')) {
            rootpath = path.join(rootpath, '..');
        }
        
        console.log('project root:' + rootpath); 
        if(fs.existsSync(workspacePath)) {
            await toolchain.svnClient.cleanup(false, rootpath);
            await toolchain.svnClient.cleanup(false, workspacePath);
            await toolchain.svnClient.revert(rootpath);
            await toolchain.svnClient.update(rootpath);
        } else{
            fs.mkdirSync(rootpath, { recursive: true });
            await toolchain.svnClient.checkout(responsitory, rootpath);
        }
        console.timeEnd('SVN更新耗时');
    }

    private async readBuiltInTpl(): Promise<TTplSheet> {
        if (toolchain.option.engine == 'unity') {
            const key = toolchain.option.platform == 'WebGL' ? toolchain.option.webglRuntime : toolchain.option.platform;
            const xf = path.join(toolchain.__dirname, '../templates/built_in_tpl.xlsx');
            const xbook = xlsx.readFile(xf);
            const xsheet = xbook.Sheets[key];
            if (xsheet != null) {
                const rows = xlsx.utils.sheet_to_json<TTplSheet>(xsheet, { blankrows: true });
                for (const r of rows) {
                    if (r.TEMPLATE == toolchain.option.executeTpl) {
                        return r;
                    }
                }
            }
        }
        console.error('could not find built-in template!');
        process.exit(1);
    }

    public async getBuildVersionInfo(): Promise<TVersionInfo | null> {
        return null;
    }

    public async getBuildConclusion(): Promise<string> {
        return '';
    }

    public async getAdditionalMessages(): Promise<IAdditionalMsg[]> {
        return [];
    }
}
